﻿//-----------------------------------------------------------------------------
// File: OutputParser.cs
// Author: Pierre Biermann (tauron031@gmail.com)
// Project: FFMPEG audio video converter class library
// Namespace(s): FFShellAPI.FFMPEG.Core
//
// Desc: Class for decoding outputs from ffmpeg tools.
//       
// (c) 2007 - 2013 by Pierre Biermann
//-----------------------------------------------------------------------------

using System;
using System.Globalization;
using System.Text.RegularExpressions;
using FFShellAPI.FFMPEG.Settings;
using System.Collections.Generic;

namespace FFShellAPI.FFMPEG.Core
{
    /// <summary>
    /// Class that parse the output data from an conversion or probing process.
    /// </summary>
    public class OutputParser
    {
        string rawData;

        /// <summary>
        /// Creates a new OutputParser instance.
        /// </summary>
        public OutputParser()
        {
            rawData = String.Empty;
        }

        /// <summary>
        /// Creates a new OutputParser instance.
        /// </summary>
        /// <param name="outputData">input data for the parser (output from ffmpeg tool)</param>
        public OutputParser(string outputData)
        {
            rawData = outputData;
        }

        #region private methods
        private string ExtractRawAudioFormat(string rawInfo)
        {
            string a = string.Empty;
            Regex re = new Regex("[A|a]udio:.*", RegexOptions.Compiled);
            Match m = re.Match(rawInfo);
            if (m.Success) a = m.Value.ToLower();
            return a.Replace("audio: ", "");
        }

        private string ExtractRawVideoFormat(string rawInfo)
        {
            string a = string.Empty;
            Regex re = new Regex("[V|v]ideo:.*", RegexOptions.Compiled);
            Match m = re.Match(rawInfo);
            if (m.Success) a = m.Value.ToLower();
            return a.Replace("video: ", "");
        }

        private AVCodec ExtractCodec(string rawData)
        {
            string _supportChars, _codecName, _codecDescription;
            Regex rex = new Regex("\\s([D|E|V|A|S|I|L|\\.]{1,8})\\s([0-9|a-z|_]{2,30})\\s(.*)", RegexOptions.Compiled);
            Match m = rex.Match(rawData);
            if (m.Success)
            {
                _supportChars = m.Groups[1].Value.Trim(' ', '\r');
                _codecName = m.Groups[2].Value;
                _codecDescription = m.Groups[3].Value.Trim(' ', '\r');
                return new AVCodec(_codecName, _codecDescription, _supportChars);
            }
            else
            {
                throw new FormatException("rawData");
            }
        }

        private string FilterCodec(string rawData)
        {
            Regex rex = new Regex("(\\w*)", RegexOptions.Compiled);
            Match m = rex.Match(rawData);
            if (m.Success)
            {
                return m.Groups[1].Value;
            }
            return rawData;
        }
        #endregion

        #region public methods
        /// <summary>
        /// Extracts the video stream format informations.
        /// </summary>
        /// <returns>VideoSettings instance</returns>
        public bool ExtractVideoFormat(VideoSettings video)
        {
            bool success = false;
            string rawVideo = ExtractRawVideoFormat(rawData);
            string[] pices = rawVideo.Split(',');
            video.CodecName = FilterCodec(pices[0]);
            Regex rex = new Regex("(\\d{2,4})x(\\d{2,4})", RegexOptions.Compiled);
            Match m = rex.Match(rawVideo);
            if (m.Success)
            {
                int width, height;
                if (Int32.TryParse(m.Groups[1].Value, out width))
                    video.PixelWidth = width;
                if (Int32.TryParse(m.Groups[2].Value, out height))
                    video.PixelHeight = height;
                success = true;
            }
            rex = new Regex("(\\d{2,5}) kb/s", RegexOptions.Compiled);
            m = rex.Match(rawVideo);
            if (m.Success)
            {
                int bitrate;
                if (Int32.TryParse(m.Groups[1].Value, out bitrate))
                    video.Bitrate = bitrate;
                success = true;
            }
            return success;
        }

        /// <summary>
        /// Extracts the audio stream format informations.
        /// </summary>
        /// <returns>AudioSettings instance</returns>
        public bool ExtractAudioFormat(AudioSettings audio)
        {
            int intValue;
            bool success = false;
            string rawAudio = ExtractRawAudioFormat(rawData);
            string[] pices = rawAudio.Split(',');
            audio.CodecName = FilterCodec(pices[0]);

            Regex rex = new Regex("(\\d{1,}) Hz", RegexOptions.Compiled);
            Match m = rex.Match(rawAudio);
            if (m.Success)
            {
                if (Int32.TryParse(m.Groups[1].Value, out intValue))
                    audio.SampleRate = intValue;
                success = true;
            }

            rex = new Regex("(\\d{1,}) kb", RegexOptions.Compiled);
            m = rex.Match(rawAudio);
            if (m.Success)
            {
                if (Int32.TryParse(m.Groups[1].Value, out intValue))
                    audio.Bitrate = intValue;
                success = true;
            }

            rex = new Regex("[S|s]tereo|[M|m]ono", RegexOptions.Compiled);
            m = rex.Match(rawAudio);
            if (m.Success)
            {
                rawAudio = m.Value.ToLower();
                if (rawAudio == "stereo") audio.Channels = 2;
                if (rawAudio == "mono") audio.Channels = 1;
                success = true;
            }
            return success;
        }

        /// <summary>
        /// Extract a list of audio and video codecs that must be previously queryed from ffmpeg.
        /// </summary>
        /// <returns>array with codecs</returns>
        public AVCodec[] GetCodecs()
        {
            List<AVCodec> codecList = new List<AVCodec>();
            string[] lines = rawData.Split('\r');
            foreach (string line in lines)
            {
                try
                {
                    codecList.Add(ExtractCodec(line));
                }
                catch (FormatException)
                {
                    continue;
                }
            }
            return codecList.ToArray();
        }

        /// <summary>
        /// Extract the current time stamp during a conversion.
        /// </summary>
        /// <param name="time">current time value</param>
        /// <returns>true if succeded otherwise false</returns>
        public bool ExtractTime(ref TimeSpan time)
        {
            Regex rex = new Regex("time=(\\d{1,2}):(\\d{1,2}):(\\d{1,2})\\.(\\d{1,3})", RegexOptions.Compiled);
            Match m = rex.Match(rawData);
            if (m.Success)
            {
                int hours = Int32.Parse(m.Groups[1].Value);
                int minutes = Int32.Parse(m.Groups[2].Value);
                int seconds = Int32.Parse(m.Groups[3].Value);
                int millis = Int32.Parse(m.Groups[4].Value);
                time = new TimeSpan(0, hours, minutes, seconds, millis);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Extract the complete time duration of the given audio or video file.
        /// </summary>
        /// <returns>true if succeded otherwise false</returns>
        public bool ExtractDuration(ref TimeSpan time)
        {
            Regex rex = new Regex("[D|d]uration: (\\d{1,2}):(\\d{1,2}):(\\d{1,2})\\.(\\d{1,3})", RegexOptions.Compiled);
            Match m = rex.Match(rawData);
            if (m.Success)
            {
                int hours = Int32.Parse(m.Groups[1].Value);
                int minutes = Int32.Parse(m.Groups[2].Value);
                int seconds = Int32.Parse(m.Groups[3].Value);
                int millis = Int32.Parse(m.Groups[4].Value);
                time = new TimeSpan(0, hours, minutes, seconds, millis);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Extract the file size in kb during a convertion process.
        /// </summary>
        /// <returns>file size</returns>
        public int ExtractSize()
        {
            Regex rex = new Regex("size=([\\s|\\d]*)", RegexOptions.Compiled);
            Match m = rex.Match(rawData);
            if (m.Success)
            {
                int size;
                if (Int32.TryParse(m.Groups[1].Value.Trim(), out size))
                    return size;
            }
            return -1;
        }

        /// <summary>
        /// Extract the current frame number during a conversion process.
        /// </summary>
        /// <returns>frame number</returns>
        public int ExtractFrame()
        {
            Regex rex = new Regex("frame=([\\s|\\d]*)", RegexOptions.Compiled);
            Match m = rex.Match(rawData);
            if (m.Success)
            {
                int frame;
                if (Int32.TryParse(m.Groups[1].Value.Trim(), out frame))
                    return frame;
            }
            return -1;
        }

        /// <summary>
        /// Extract the value for frames per second that is currently handled by the encoder during encoding process.
        /// </summary>
        /// <returns>frames per second</returns>
        public int ExtractFramesPerSecond()
        {
            Regex rex = new Regex("fps=([\\s|\\d]*)", RegexOptions.Compiled);
            Match m = rex.Match(rawData);
            if (m.Success)
            {
                int fps;
                if (Int32.TryParse(m.Groups[1].Value.Trim(), out fps))
                    return fps;
            }
            return -1;
        }

        /// <summary>
        /// Extracts the bitrate for the frame that is currently handled by the encoder during encoding process.
        /// </summary>
        /// <returns>current bit rate</returns>
        public double ExtractCurrentBitrate()
        {
            Regex rex = new Regex("bitrate=([\\s|\\d]*)", RegexOptions.Compiled);
            Match m = rex.Match(rawData);
            if (m.Success)
            {
                double bitrate;
                NumberFormatInfo provider = new NumberFormatInfo();
                provider.CurrencyDecimalSeparator = ".";
                if (Double.TryParse(m.Groups[1].Value.Trim(), NumberStyles.AllowDecimalPoint, provider, out bitrate))
                    return bitrate;
            }
            return -1d;
        }

        /// <summary>
        /// Extract the main bitrate for the entire media file.
        /// </summary>
        /// <returns>bitrate in kb/s</returns>
        public int ExtractBitrate()
        {
            int bitrate = -1;
            Regex re = new Regex("[B|b]itrate: (\\d{1,})", RegexOptions.Compiled);
            Match m = re.Match(rawData);
            if (m.Success)
            {
                Int32.TryParse(m.Groups[1].Value, out bitrate);
            }
            return bitrate;
        }
        #endregion

        #region public properties
        /// <summary>
        /// Gets or sets the input data string fo the parser.
        /// </summary>
        public virtual string RawData
        {
            get { return rawData; }
            set { rawData = value; }
        } 
        #endregion
    }
}
